#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sconfig.h>
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>

// file path
#define __PWD_PATH "/etc/passwd"

// separator
#define __PWD_SEPARTOR ':'

// length
#define __PWD_LEN 64
#define __PWD_BUFF_LEN (__PWD_LEN + 1)

static char *pwd_buff = NULL;
static char *pwd_ptr = NULL;
static passwd_t __pwd = {NULL, NULL, 0, 0, NULL, NULL, NULL};

static int __pw_load()
{
    int pwdfd = -1;
    int64_t sz;

    if (!pwd_buff)
    {
        // open file
        pwdfd = open(__PWD_PATH, O_RDONLY);
        if (pwdfd < 0)
        {
            _set_errno(ENOFILE);
            return -1;
        }

        // file size
        lseek(pwdfd, 0, SEEK_END);
        sz = tell(pwdfd);

        if (sz < 0)
        {
            _set_errno(ENOFILE);
            close(pwdfd);
            return -1;
        }

        // seek reset
        lseek(pwdfd, 0, SEEK_SET);
        pwd_buff = malloc(sz);
        if (!pwd_buff)
        {
            _set_errno(ENOMEM);
            close(pwdfd);
            return -1;
        }

        // read file
        if (read(pwdfd, pwd_buff, sz) < 0)
        {
            _set_errno(EIO);
            close(pwdfd);
            free(pwd_buff);
            return -1;
        }

        // close file
        close(pwdfd);

        pwd_ptr = pwd_buff;

        // init pwd
        if (!__pwd.pw_name && !__pwd.pw_password)
        {
            __pwd.pw_uid = 0;
            __pwd.pw_gid = 0;
            assert(__pwd.pw_name = malloc(__PWD_LEN));
            assert(__pwd.pw_password = malloc(__PWD_LEN));
            assert(__pwd.pw_gecos = malloc(__PWD_LEN));
            assert(__pwd.pw_shell = malloc(MAX_PATH_LEN));
            assert(__pwd.pw_dir = malloc(MAX_PATH_LEN));
        }

        return 0;
    }
}

static int __pw_free()
{
    if (pwd_buff)
    {
        free(pwd_buff);
        pwd_buff = NULL;
        pwd_ptr = NULL;
        return 0;
    }
    return -1;
}

static void __pw_line(char *line)
{
    char old_separator = sconfig_get_separtor();
    char buff[__PWD_BUFF_LEN];
    char *p = line;

    sconfig_set_separtor(__PWD_SEPARTOR);
    if (!line)
        return;
    // name
    memset(buff, 0, __PWD_BUFF_LEN);
    assert((p = sconfig_read(p, buff, __PWD_BUFF_LEN)));
    strcpy(__pwd.pw_name, buff);

    // passwd
    memset(buff, 0, __PWD_BUFF_LEN);
    assert((p = sconfig_read(p, buff, __PWD_BUFF_LEN)));
    strcpy(__pwd.pw_password, buff);

    // uid
    memset(buff, 0, __PWD_BUFF_LEN);
    assert((p = sconfig_read(p, buff, __PWD_BUFF_LEN)));
    __pwd.pw_uid = atoi(buff);

    // gid
    memset(buff, 0, __PWD_BUFF_LEN);
    assert((p = sconfig_read(p, buff, __PWD_BUFF_LEN)));
    __pwd.pw_gid = atoi(buff);

    // dir
    memset(buff, 0, __PWD_BUFF_LEN);
    assert((p = sconfig_read(p, buff, __PWD_BUFF_LEN)));
    strcpy(__pwd.pw_dir, buff);

    // gecos
    memset(__pwd.pw_gecos, 0, __PWD_LEN);
    assert((p = sconfig_read(p, __pwd.pw_gecos, __PWD_LEN)));

    // shell
    memset(__pwd.pw_shell, 0, MAX_PATH_LEN);
    assert((p = sconfig_read(p, __pwd.pw_shell, MAX_PATH_LEN)));

    // restore old separator
    sconfig_set_separtor(old_separator);
}

void setpwent()
{
    if (__pw_load())
        perror("pwd: __pw_load failed!\n");
}

int endpwent()
{
    if (__pw_free())
    {
        perror("pwd: __pw_free failed!\n");
    }
}

int getpw(uid_t uid, char *buff)
{
    passwd_t *ptr;

    setpwent();
    while ((ptr = getpwent()))
    {
        if (uid == ptr->pw_uid)
        {
            strcpy(buff, ptr->pw_name);
            endpwent();
            return 0;
        }
        endpwent();
    }
    return -1;
}

passwd_t *getpwent()
{
    char pw_line[256] = {0};

    if (!pwd_ptr)
        return NULL;

    pwd_ptr = sconfig_read_line(pwd_ptr, pw_line, 256);
    if (!pwd_ptr)
        return NULL;
    __pw_line(pwd_ptr);
    return &__pwd;
}

passwd_t *getpwbyname(const char *name)
{
    passwd_t *ptr;

    setpwent();
    while ((ptr = getpwent()))
    {
        if (!strcmp(name, ptr->pw_name))
        {
            break;
        }
    }
    endpwent();
    return ptr;
}

passwd_t *getpwbyuid(uid_t uid)
{
    passwd_t *ptr;
    setpwent();
    while ((ptr = getpwent()))
    {
        if (uid == ptr->pw_uid)
        {
            break;
        }
    }
    return ptr;
}